#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
01. NumPy练习题集
包含从基础到高级的NumPy练习题和解答

文件: 01_numpy_exercises.py
作者: Python学习展示项目
描述: 通过实际练习掌握NumPy的核心功能
"""

import numpy as np
import pytest
from typing import Tuple, List, Union

# ============================================================================
# 基础练习题 (Level 1: Beginner)
# ============================================================================

class BasicNumpyExercises:
    """基础NumPy练习题"""
    
    @staticmethod
    def exercise_1_create_arrays():
        """
        练习1：创建数组
        
        任务：
        1. 创建一个包含数字1到10的一维数组
        2. 创建一个3x3的全零矩阵
        3. 创建一个2x4的全一矩阵
        4. 创建一个5x5的单位矩阵
        
        返回：tuple，包含上述四个数组
        """
        # TODO: 实现你的解决方案
        arr_1to10 = np.arange(1, 11)
        zeros_matrix = np.zeros((3, 3))
        ones_matrix = np.ones((2, 4))
        identity_matrix = np.eye(5)
        
        return arr_1to10, zeros_matrix, ones_matrix, identity_matrix
    
    @staticmethod
    def exercise_2_array_operations():
        """
        练习2：数组基本运算
        
        任务：
        给定两个数组 a = [1, 2, 3, 4, 5] 和 b = [6, 7, 8, 9, 10]
        1. 计算 a + b
        2. 计算 a * b
        3. 计算 a 的平方
        4. 计算 b 的平方根
        
        返回：tuple，包含上述四个计算结果
        """
        a = np.array([1, 2, 3, 4, 5])
        b = np.array([6, 7, 8, 9, 10])
        
        # TODO: 实现你的解决方案
        addition = a + b
        multiplication = a * b
        a_squared = a ** 2
        b_sqrt = np.sqrt(b)
        
        return addition, multiplication, a_squared, b_sqrt
    
    @staticmethod
    def exercise_3_array_indexing():
        """
        练习3：数组索引和切片
        
        任务：
        给定数组 arr = np.arange(20).reshape(4, 5)
        1. 获取第2行（索引为1）
        2. 获取第3列（索引为2）
        3. 获取左上角2x2的子矩阵
        4. 获取所有大于10的元素
        
        返回：tuple，包含上述四个结果
        """
        arr = np.arange(20).reshape(4, 5)
        
        # TODO: 实现你的解决方案
        row_2 = arr[1, :]
        col_3 = arr[:, 2]
        submatrix_2x2 = arr[:2, :2]
        elements_gt_10 = arr[arr > 10]
        
        return row_2, col_3, submatrix_2x2, elements_gt_10
    
    @staticmethod
    def exercise_4_statistical_functions():
        """
        练习4：统计函数
        
        任务：
        给定数组 data = np.random.normal(50, 10, 100)（正态分布数据）
        1. 计算平均值
        2. 计算标准差
        3. 计算最大值和最小值
        4. 计算中位数
        
        返回：tuple，包含 (mean, std, max_val, min_val, median)
        """
        np.random.seed(42)  # 确保结果可重现
        data = np.random.normal(50, 10, 100)
        
        # TODO: 实现你的解决方案
        mean = np.mean(data)
        std = np.std(data)
        max_val = np.max(data)
        min_val = np.min(data)
        median = np.median(data)
        
        return mean, std, max_val, min_val, median

# ============================================================================
# 中级练习题 (Level 2: Intermediate)
# ============================================================================

class IntermediateNumpyExercises:
    """中级NumPy练习题"""
    
    @staticmethod
    def exercise_5_matrix_operations():
        """
        练习5：矩阵运算
        
        任务：
        给定两个3x3矩阵 A 和 B
        A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
        B = [[9, 8, 7], [6, 5, 4], [3, 2, 1]]
        
        1. 计算矩阵乘法 A @ B
        2. 计算 A 的转置
        3. 计算 A 的行列式
        4. 尝试计算 A 的逆矩阵（如果不可逆，返回None）
        
        返回：tuple，包含 (matrix_mult, transpose, determinant, inverse)
        """
        A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        B = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])
        
        # TODO: 实现你的解决方案
        matrix_mult = A @ B
        transpose = A.T
        determinant = np.linalg.det(A)
        
        try:
            inverse = np.linalg.inv(A)
        except np.linalg.LinAlgError:
            inverse = None
        
        return matrix_mult, transpose, determinant, inverse
    
    @staticmethod
    def exercise_6_broadcasting():
        """
        练习6：广播机制
        
        任务：
        1. 创建一个3x4的矩阵，元素为1到12
        2. 创建一个长度为4的数组 [1, 2, 3, 4]
        3. 将矩阵的每一行都加上这个数组
        4. 创建一个长度为3的数组 [10, 20, 30]，将矩阵的每一列都乘以对应的值
        
        返回：tuple，包含 (original_matrix, row_addition, column_multiplication)
        """
        # TODO: 实现你的解决方案
        original_matrix = np.arange(1, 13).reshape(3, 4)
        row_array = np.array([1, 2, 3, 4])
        col_array = np.array([10, 20, 30])
        
        row_addition = original_matrix + row_array
        column_multiplication = original_matrix * col_array.reshape(-1, 1)
        
        return original_matrix, row_addition, column_multiplication
    
    @staticmethod
    def exercise_7_fancy_indexing():
        """
        练习7：花式索引
        
        任务：
        给定数组 arr = np.arange(100).reshape(10, 10)
        1. 使用花式索引选择第1, 3, 5行
        2. 使用花式索引选择第2, 4, 6列
        3. 同时选择特定的行和列：第1,3行的第2,4列
        4. 使用布尔索引选择所有能被3整除的元素
        
        返回：tuple，包含上述四个结果
        """
        arr = np.arange(100).reshape(10, 10)
        
        # TODO: 实现你的解决方案
        selected_rows = arr[[1, 3, 5]]
        selected_cols = arr[:, [2, 4, 6]]
        selected_both = arr[[1, 3]][:, [2, 4]]
        divisible_by_3 = arr[arr % 3 == 0]
        
        return selected_rows, selected_cols, selected_both, divisible_by_3
    
    @staticmethod
    def exercise_8_array_manipulation():
        """
        练习8：数组操作
        
        任务：
        1. 创建两个数组：a = [1, 2, 3] 和 b = [4, 5, 6]
        2. 水平连接这两个数组
        3. 垂直连接这两个数组（需要先reshape）
        4. 将数组 [1, 2, 3, 4, 5, 6] 重塑为 2x3 矩阵
        5. 将上述矩阵展平为一维数组
        
        返回：tuple，包含上述操作的结果
        """
        a = np.array([1, 2, 3])
        b = np.array([4, 5, 6])
        
        # TODO: 实现你的解决方案
        horizontal_concat = np.hstack((a, b))
        vertical_concat = np.vstack((a, b))
        reshaped_matrix = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3)
        flattened = reshaped_matrix.flatten()
        
        return horizontal_concat, vertical_concat, reshaped_matrix, flattened

# ============================================================================
# 高级练习题 (Level 3: Advanced)
# ============================================================================

class AdvancedNumpyExercises:
    """高级NumPy练习题"""
    
    @staticmethod
    def exercise_9_eigenvalues_eigenvectors():
        """
        练习9：特征值和特征向量
        
        任务：
        给定矩阵 A = [[4, -2], [1, 1]]
        1. 计算特征值和特征向量
        2. 验证特征值和特征向量的正确性（A * v = λ * v）
        3. 计算矩阵的迹（对角线元素之和）
        4. 验证迹等于特征值之和
        
        返回：tuple，包含 (eigenvalues, eigenvectors, trace, verification)
        """
        A = np.array([[4, -2], [1, 1]])
        
        # TODO: 实现你的解决方案
        eigenvalues, eigenvectors = np.linalg.eig(A)
        trace = np.trace(A)
        
        # 验证第一个特征值和特征向量
        v1 = eigenvectors[:, 0]
        lambda1 = eigenvalues[0]
        verification = np.allclose(A @ v1, lambda1 * v1)
        
        return eigenvalues, eigenvectors, trace, verification
    
    @staticmethod
    def exercise_10_solve_linear_system():
        """
        练习10：解线性方程组
        
        任务：
        解线性方程组：
        2x + 3y = 7
        x - y = 1
        
        1. 将方程组表示为矩阵形式 Ax = b
        2. 使用numpy求解
        3. 验证解的正确性
        
        返回：tuple，包含 (coefficient_matrix, constants, solution, verification)
        """
        # TODO: 实现你的解决方案
        # 系数矩阵 A
        A = np.array([[2, 3], [1, -1]])
        # 常数向量 b
        b = np.array([7, 1])
        
        # 求解
        solution = np.linalg.solve(A, b)
        
        # 验证
        verification = np.allclose(A @ solution, b)
        
        return A, b, solution, verification
    
    @staticmethod
    def exercise_11_polynomial_fitting():
        """
        练习11：多项式拟合
        
        任务：
        给定数据点 x = [0, 1, 2, 3, 4] 和 y = [1, 3, 7, 13, 21]
        1. 使用2次多项式拟合数据
        2. 计算拟合的多项式系数
        3. 计算拟合值
        4. 计算R²（决定系数）
        
        返回：tuple，包含 (coefficients, fitted_values, r_squared)
        """
        x = np.array([0, 1, 2, 3, 4])
        y = np.array([1, 3, 7, 13, 21])
        
        # TODO: 实现你的解决方案
        # 2次多项式拟合
        coefficients = np.polyfit(x, y, 2)
        
        # 计算拟合值
        fitted_values = np.polyval(coefficients, x)
        
        # 计算R²
        ss_res = np.sum((y - fitted_values) ** 2)
        ss_tot = np.sum((y - np.mean(y)) ** 2)
        r_squared = 1 - (ss_res / ss_tot)
        
        return coefficients, fitted_values, r_squared
    
    @staticmethod
    def exercise_12_image_processing():
        """
        练习12：图像处理基础
        
        任务：
        创建一个简单的"图像"处理示例
        1. 创建一个10x10的随机"图像"（值在0-255之间）
        2. 实现简单的边缘检测（使用Sobel算子的简化版本）
        3. 实现图像的旋转（90度）
        4. 计算图像的直方图（统计每个像素值的出现次数）
        
        返回：tuple，包含 (original_image, edges, rotated, histogram)
        """
        np.random.seed(42)
        
        # TODO: 实现你的解决方案
        # 创建随机图像
        original_image = np.random.randint(0, 256, (10, 10), dtype=np.uint8)
        
        # 简单边缘检测（水平和垂直梯度）
        grad_x = np.diff(original_image, axis=1)
        grad_y = np.diff(original_image, axis=0)
        
        # 创建与原图像相同大小的边缘图像
        edges = np.zeros_like(original_image, dtype=np.float32)
        edges[:-1, :-1] = np.sqrt(grad_x[:-1, :]**2 + grad_y[:, :-1]**2)
        
        # 旋转90度
        rotated = np.rot90(original_image)
        
        # 计算直方图
        histogram = np.histogram(original_image, bins=256, range=(0, 256))[0]
        
        return original_image, edges, rotated, histogram

# ============================================================================
# 实战练习题 (Level 4: Real-world Applications)
# ============================================================================

class RealWorldNumpyExercises:
    """实战NumPy练习题"""
    
    @staticmethod
    def exercise_13_stock_analysis():
        """
        练习13：股票数据分析
        
        任务：
        模拟股票价格数据分析
        1. 生成252天的股票价格数据（使用随机游走）
        2. 计算日收益率
        3. 计算移动平均线（20天和50天）
        4. 计算波动率（收益率的标准差）
        5. 识别价格突破（价格超过20日均线的天数）
        
        返回：tuple，包含分析结果
        """
        np.random.seed(42)
        
        # TODO: 实现你的解决方案
        # 生成股票价格数据
        initial_price = 100
        daily_returns = np.random.normal(0.001, 0.02, 252)
        prices = [initial_price]
        
        for ret in daily_returns:
            new_price = prices[-1] * (1 + ret)
            prices.append(new_price)
        
        prices = np.array(prices[1:])
        
        # 计算日收益率
        returns = np.diff(prices) / prices[:-1]
        
        # 计算移动平均线
        def moving_average(data, window):
            return np.convolve(data, np.ones(window)/window, mode='valid')
        
        ma_20 = moving_average(prices, 20)
        ma_50 = moving_average(prices, 50)
        
        # 计算波动率（年化）
        volatility = np.std(returns) * np.sqrt(252)
        
        # 识别突破（价格超过20日均线）
        # 需要对齐数组长度
        prices_aligned = prices[19:]  # 去掉前19天
        breakthrough_days = np.sum(prices_aligned > ma_20)
        
        return prices, returns, ma_20, ma_50, volatility, breakthrough_days
    
    @staticmethod
    def exercise_14_signal_processing():
        """
        练习14：信号处理
        
        任务：
        创建和分析一个复合信号
        1. 生成一个包含多个频率成分的信号
        2. 添加噪声
        3. 实现简单的滤波器（移动平均滤波）
        4. 计算信号的功率谱密度（简化版本）
        
        返回：tuple，包含信号处理结果
        """
        # TODO: 实现你的解决方案
        # 生成时间轴
        t = np.linspace(0, 1, 1000, endpoint=False)
        
        # 生成复合信号（多个正弦波）
        signal = (np.sin(2 * np.pi * 5 * t) +  # 5Hz
                 0.5 * np.sin(2 * np.pi * 10 * t) +  # 10Hz
                 0.3 * np.sin(2 * np.pi * 20 * t))   # 20Hz
        
        # 添加噪声
        np.random.seed(42)
        noise = 0.2 * np.random.randn(len(signal))
        noisy_signal = signal + noise
        
        # 移动平均滤波
        window_size = 10
        filtered_signal = np.convolve(noisy_signal, 
                                    np.ones(window_size)/window_size, 
                                    mode='same')
        
        # 简单的功率谱密度（使用FFT）
        fft = np.fft.fft(filtered_signal)
        power_spectrum = np.abs(fft)**2
        frequencies = np.fft.fftfreq(len(signal), t[1] - t[0])
        
        return signal, noisy_signal, filtered_signal, power_spectrum, frequencies
    
    @staticmethod
    def exercise_15_monte_carlo_simulation():
        """
        练习15：蒙特卡洛模拟
        
        任务：
        使用蒙特卡洛方法估算π的值
        1. 在单位正方形内随机生成点
        2. 计算落在单位圆内的点的比例
        3. 使用这个比例估算π
        4. 分析估算精度与样本数量的关系
        
        返回：tuple，包含 (estimated_pi, accuracy, sample_points)
        """
        np.random.seed(42)
        
        # TODO: 实现你的解决方案
        n_samples = 100000
        
        # 生成随机点
        x = np.random.uniform(-1, 1, n_samples)
        y = np.random.uniform(-1, 1, n_samples)
        
        # 计算距离原点的距离
        distances = np.sqrt(x**2 + y**2)
        
        # 计算落在单位圆内的点
        inside_circle = distances <= 1
        n_inside = np.sum(inside_circle)
        
        # 估算π
        estimated_pi = 4 * n_inside / n_samples
        
        # 计算精度
        accuracy = abs(estimated_pi - np.pi) / np.pi * 100
        
        # 返回一些样本点用于可视化
        sample_points = np.column_stack((x[:1000], y[:1000], inside_circle[:1000]))
        
        return estimated_pi, accuracy, sample_points

# ============================================================================
# 测试函数
# ============================================================================

def test_basic_exercises():
    """测试基础练习题"""
    print("测试基础练习题...")
    
    # 测试练习1
    arr_1to10, zeros, ones, identity = BasicNumpyExercises.exercise_1_create_arrays()
    assert np.array_equal(arr_1to10, np.arange(1, 11))
    assert zeros.shape == (3, 3) and np.all(zeros == 0)
    assert ones.shape == (2, 4) and np.all(ones == 1)
    assert identity.shape == (5, 5) and np.allclose(identity, np.eye(5))
    
    # 测试练习2
    add, mult, sq, sqrt = BasicNumpyExercises.exercise_2_array_operations()
    expected_add = np.array([7, 9, 11, 13, 15])
    assert np.array_equal(add, expected_add)
    
    # 测试练习3
    row, col, sub, gt10 = BasicNumpyExercises.exercise_3_array_indexing()
    assert len(row) == 5
    assert len(col) == 4
    assert sub.shape == (2, 2)
    
    # 测试练习4
    mean, std, max_val, min_val, median = BasicNumpyExercises.exercise_4_statistical_functions()
    assert isinstance(mean, (int, float, np.number))
    assert isinstance(std, (int, float, np.number))
    
    print("✅ 基础练习题测试通过！")

def test_intermediate_exercises():
    """测试中级练习题"""
    print("测试中级练习题...")
    
    # 测试练习5
    mult, trans, det, inv = IntermediateNumpyExercises.exercise_5_matrix_operations()
    assert mult.shape == (3, 3)
    assert trans.shape == (3, 3)
    assert isinstance(det, (int, float, np.number))
    
    # 测试练习6
    orig, row_add, col_mult = IntermediateNumpyExercises.exercise_6_broadcasting()
    assert orig.shape == (3, 4)
    assert row_add.shape == (3, 4)
    assert col_mult.shape == (3, 4)
    
    # 测试练习7
    rows, cols, both, div3 = IntermediateNumpyExercises.exercise_7_fancy_indexing()
    assert rows.shape == (3, 10)
    assert cols.shape == (10, 3)
    
    # 测试练习8
    h_concat, v_concat, reshaped, flat = IntermediateNumpyExercises.exercise_8_array_manipulation()
    assert len(h_concat) == 6
    assert v_concat.shape == (2, 3)
    assert reshaped.shape == (2, 3)
    assert len(flat) == 6
    
    print("✅ 中级练习题测试通过！")

def test_advanced_exercises():
    """测试高级练习题"""
    print("测试高级练习题...")
    
    # 测试练习9
    eigenvals, eigenvecs, trace, verify = AdvancedNumpyExercises.exercise_9_eigenvalues_eigenvectors()
    assert len(eigenvals) == 2
    assert eigenvecs.shape == (2, 2)
    assert isinstance(trace, (int, float, np.number))
    assert isinstance(verify, (bool, np.bool_))
    
    # 测试练习10
    A, b, sol, verify = AdvancedNumpyExercises.exercise_10_solve_linear_system()
    assert A.shape == (2, 2)
    assert len(b) == 2
    assert len(sol) == 2
    assert verify
    
    # 测试练习11
    coeffs, fitted, r2 = AdvancedNumpyExercises.exercise_11_polynomial_fitting()
    assert len(coeffs) == 3  # 2次多项式有3个系数
    assert len(fitted) == 5
    assert 0 <= r2 <= 1
    
    # 测试练习12
    img, edges, rotated, hist = AdvancedNumpyExercises.exercise_12_image_processing()
    assert img.shape == (10, 10)
    assert rotated.shape == (10, 10)
    assert len(hist) == 256
    
    print("✅ 高级练习题测试通过！")

def test_real_world_exercises():
    """测试实战练习题"""
    print("测试实战练习题...")
    
    # 测试练习13
    prices, returns, ma20, ma50, vol, breakthrough = RealWorldNumpyExercises.exercise_13_stock_analysis()
    assert len(prices) == 252
    assert len(returns) == 251
    assert len(ma20) == 233  # 252 - 20 + 1
    assert len(ma50) == 203  # 252 - 50 + 1
    assert isinstance(vol, (int, float, np.number))
    assert isinstance(breakthrough, (int, np.integer))
    
    # 测试练习14
    signal, noisy, filtered, power, freqs = RealWorldNumpyExercises.exercise_14_signal_processing()
    assert len(signal) == 1000
    assert len(noisy) == 1000
    assert len(filtered) == 1000
    assert len(power) == 1000
    assert len(freqs) == 1000
    
    # 测试练习15
    pi_est, accuracy, samples = RealWorldNumpyExercises.exercise_15_monte_carlo_simulation()
    assert 2.5 < pi_est < 3.5  # 合理的π估算范围
    assert 0 <= accuracy <= 100
    assert samples.shape == (1000, 3)
    
    print("✅ 实战练习题测试通过！")

def run_all_tests():
    """运行所有测试"""
    print("=" * 60)
    print("NumPy 练习题测试")
    print("=" * 60)
    
    test_basic_exercises()
    test_intermediate_exercises()
    test_advanced_exercises()
    test_real_world_exercises()
    
    print("\n" + "=" * 60)
    print("🎉 所有NumPy练习题测试通过！")
    print("=" * 60)

def demonstrate_exercises():
    """演示练习题的解决方案"""
    print("NumPy 练习题演示")
    print("=" * 80)
    
    print("\n📚 基础练习题演示:")
    print("-" * 40)
    
    # 演示练习1
    print("\n练习1: 创建数组")
    arr_1to10, zeros, ones, identity = BasicNumpyExercises.exercise_1_create_arrays()
    print(f"1到10的数组: {arr_1to10}")
    print(f"3x3零矩阵:\n{zeros}")
    
    # 演示练习13（股票分析）
    print("\n📈 实战练习演示: 股票分析")
    print("-" * 40)
    prices, returns, ma20, ma50, vol, breakthrough = RealWorldNumpyExercises.exercise_13_stock_analysis()
    print(f"股票价格范围: ${np.min(prices):.2f} - ${np.max(prices):.2f}")
    print(f"年化波动率: {vol:.2%}")
    print(f"突破20日均线的天数: {breakthrough}")
    
    # 演示练习15（蒙特卡洛）
    print("\n🎲 蒙特卡洛模拟演示:")
    print("-" * 40)
    pi_est, accuracy, samples = RealWorldNumpyExercises.exercise_15_monte_carlo_simulation()
    print(f"π的估算值: {pi_est:.6f}")
    print(f"真实π值: {np.pi:.6f}")
    print(f"估算精度: {accuracy:.2f}% 误差")

if __name__ == "__main__":
    demonstrate_exercises()
    print("\n")
    run_all_tests()